<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="icon" href="images/favicon.ico" />
    <link rel="canonical" href="http://bookshelfjs.org" />
    <title>Bookshelf.js | lib/sync.js</title>
    <!--
    ._.                                                      ._.
    | |    ______             _        _          _  __      | |
    | |    | ___ \           | |      | |        | |/ _|     | |
    | |    | |_/ / ___   ___ | | _____| |__   ___| | |_      | |
    | |    | ___ \/ _ \ / _ \| |/ / __| '_ \ / _ \ |  _|     | |
    | |    | |_/ / (_) | (_) |   <\__ \ | | |  __/ | |       | |
    | |____\____/_\___/_\___/|_|\_\___/_|_|_|\___|_|_|_______| |
    |                                                          |
    \________\  /__________________________________\  /________/
              \/                                    \/
    -->
    <script src="scripts/prettify/prettify.js"></script>
    <script src="scripts/prettify/lang-css.js"></script>
    <!--[if lt IE 9]>
      <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->

    <!-- Import fonts from Open Font Library -->
    <link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/carlito" rel="stylesheet" type="text/css"/>
    <link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/average-mono" rel="stylesheet" type="text/css"/>
    <link rel="stylesheet" media="screen" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css"/>

    <link type="text/css" rel="stylesheet" href="styles/main.css">
  </head>
  <body class="minami">
    <nav class="main-navbar">
      <a href="index.html"><img class="nav-logo" src="images/bookshelf-icon-inverted.svg" />Bookshelf.js
</a>
      <label for="show-menu" class="show-menu">
        <i class="fa fa-bars fa-lg" aria-hidden="true"></i>
      </label>
      <input type="checkbox" id="show-menu" role="button" checked>
      <ul id="menu">
        
          <li>
            <a href="tutorials.html"><i class="fa fa-file-text" aria-hidden="true"></i>Guides
</a>
          </li>
        
        
          <li>
            <a href="api.html"><i class="fa fa-book" aria-hidden="true"></i>API Reference
</a>
          </li>
        
        <li>
          <a href="https://github.com/bookshelf/bookshelf"><span class="fa fa-github"></span></a>
        </li>
      </ul>
    </nav>

    <div class="page-content">
      

      <div class="main-container source">
        <div class="main-column">
          
            <h1 class="page-title">lib/sync.js</h1>
          

          


    
    <section>
        <article>
            <pre class="prettyprint source linenums"><code>// Sync
// ---------------
'use strict';

const _ = require('lodash');
const Promise = require('bluebird');
const validLocks = ['forShare', 'forUpdate'];

function supportsReturning(client = {}) {
  if (!client.config || !client.config.client) return false;
  return ['postgresql', 'postgres', 'pg', 'oracle', 'mssql'].includes(client.config.client);
}

// Sync is the dispatcher for any database queries,
// taking the "syncing" `model` or `collection` being queried, along with
// a hash of options that are used in the various query methods.
// If the `transacting` option is set, the query is assumed to be
// part of a transaction, and this information is passed along to `Knex`.
const Sync = function(syncing, options) {
  options = options || {};
  this.query = syncing.query();
  this.syncing = syncing.resetQuery();
  this.options = options;
  if (options.debug) this.query.debug();
  if (options.transacting) {
    this.query.transacting(options.transacting);
    if (validLocks.indexOf(options.lock) > -1) this.query[options.lock]();
  }
  if (options.withSchema) this.query.withSchema(options.withSchema);
};

_.extend(Sync.prototype, {
  // Prefix all keys of the passed in object with the
  // current table name
  prefixFields: function(fields) {
    const tableName = this.syncing.tableName;
    const prefixed = {};
    for (const key in fields) {
      prefixed[tableName + '.' + key] = fields[key];
    }
    return prefixed;
  },

  // Select the first item from the database - only used by models.
  first: Promise.method(function(attributes) {
    const model = this.syncing;
    const query = this.query;

    // We'll never use an JSON object for a search, because even
    // PostgreSQL, which has JSON type columns, does not support the `=`
    // operator.
    //
    // NOTE: `_.omit` returns an empty object, even if attributes are null.
    const whereAttributes = _.omitBy(attributes, (attribute, name) => {
      return _.isPlainObject(attribute) || name === model.idAttribute;
    });
    const formattedAttributes = model.format(whereAttributes);

    if (model.idAttribute in attributes) {
      formattedAttributes[model.idAttribute] = attributes[model.idAttribute];
    }

    if (!_.isEmpty(formattedAttributes)) query.where(this.prefixFields(formattedAttributes));
    query.limit(1);

    return this.select();
  }),

  // Runs a `count` query on the database, adding any necessary relational
  // constraints. Returns a promise that resolves to an integer count.
  count: Promise.method(function(column) {
    const knex = this.query,
      options = this.options,
      relatedData = this.syncing.relatedData,
      fks = {};

    return Promise.bind(this)
      .then(function() {
        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);
              relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
            } else if (relatedData.type === 'hasMany') {
              const fk = relatedData.key('foreignKey');
              knex.where(fk, relatedData.parentFk);
            }
          });
      })
      .then(function() {
        options.query = knex;

        /**
         * Counting event.
         *
         * Fired before a `count` query. A promise may be
         * returned from the event handler for async behaviour.
         *
         * @event Model#counting
         * @tutorial events
         * @param {Model}  model    The model firing the event.
         * @param {Object} options  Options object passed to {@link Model#count count}.
         * @returns {Promise}
         */
        return this.syncing.triggerThen('counting', this.syncing, options);
      })
      .then(function() {
        return knex.count((column || '*') + ' as count');
      })
      .then(function(rows) {
        return rows[0].count;
      });
  }),

  // Runs a `select` query on the database, adding any necessary relational
  // constraints, resetting the query when complete. If there are results and
  // eager loaded relations, those are fetched and returned on the model before
  // the promise is resolved. Any `success` handler passed in the
  // options will be called - used by both models &amp; collections.
  select: Promise.method(function() {
    const knex = this.query;
    const options = this.options;
    const relatedData = this.syncing.relatedData;
    const fks = {};
    let columns = null;

    // Check if any `select` style statements have been called with column
    // specifications. This could include `distinct()` with no arguments, which
    // does not affect inform the columns returned.
    const queryContainsColumns = _(knex._statements)
      .filter({grouping: 'columns'})
      .some('value.length');

    return Promise.bind(this)
      .then(function() {
        // Set the query builder on the options, in-case we need to
        // access in the `fetching` event handlers.
        options.query = knex;

        // Inject all appropriate select costraints dealing with the relation
        // into the `knex` query builder for the current instance.
        if (relatedData)
          return Promise.try(function() {
            if (relatedData.isThrough()) {
              fks[relatedData.key('foreignKey')] = relatedData.parentFk;
              const through = new relatedData.throughTarget(fks);

              return through.triggerThen('fetching', through, relatedData.pivotColumns, options).then(function() {
                relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
              });
            }
          });
      })
      .tap(() => {
        // If this is a relation, apply the appropriate constraints.
        if (relatedData) {
          relatedData.selectConstraints(knex, options);
        } else {
          // Call the function, if one exists, to constrain the eager loaded query.
          if (options._beforeFn) options._beforeFn.call(knex, knex);

          if (options.columns) {
            // Normalize single column name into array.
            columns = Array.isArray(options.columns) ? options.columns : [options.columns];
          } else if (!queryContainsColumns) {
            // If columns have already been selected via the `query` method
            // we will use them. Otherwise, select all columns in this table.
            columns = [_.result(this.syncing, 'tableName') + '.*'];
          }
        }

        // Set the query builder on the options, for access in the `fetching`
        // event handlers.
        options.query = knex;

        /**
         * Fired before a `fetch` operation. A promise may be returned from the event handler for
         * async behaviour.
         *
         * @example
         * const MyModel = bookshelf.model('MyModel', {
         *   initialize() {
         *     this.on('fetching', function(model, columns, options) {
         *       options.query.where('status', 'active')
         *     })
         *   }
         * })
         *
         * @event Model#fetching
         * @tutorial events
         * @param {Model} model The model which is about to be fetched.
         * @param {string[]} columns The columns to be retrieved by the query.
         * @param {Object} options Options object passed to {@link Model#fetch fetch}.
         * @param {QueryBuilder} options.query
         *   Query builder to be used for fetching. This can be used to modify or add to the query
         *   before it is executed. See example above.
         * @return {Promise}
         */
        return this.syncing.triggerThen('fetching', this.syncing, columns, options);
      })
      .then(() => knex.select(columns));
  }),

  // Issues an `insert` command on the query - only used by models.
  insert: Promise.method(function() {
    const syncing = this.syncing;
    return this.query.insert(
      syncing.format(_.extend(Object.create(null), syncing.attributes)),
      supportsReturning(this.query.client) &amp;&amp; this.options.autoRefresh !== false ? '*' : null
    );
  }),

  // Issues an `update` command on the query - only used by models.
  update: Promise.method(function(attrs) {
    const syncing = this.syncing,
      query = this.query;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be updated without a "where" clause or an idAttribute.');
    }
    var updating = syncing.format(_.extend(Object.create(null), attrs));
    if (syncing.id === updating[syncing.idAttribute]) {
      delete updating[syncing.idAttribute];
    }
    if (supportsReturning(query.client) &amp;&amp; this.options.autoRefresh !== false) query.returning('*');
    return query.update(updating);
  }),

  // Issues a `delete` command on the query.
  del: Promise.method(function() {
    const query = this.query,
      syncing = this.syncing;
    if (syncing.id != null) query.where(syncing.format({[syncing.idAttribute]: syncing.id}));
    if (_.filter(query._statements, {grouping: 'where'}).length === 0) {
      throw new Error('A model cannot be destroyed without a "where" clause or an idAttribute.');
    }
    return this.query.del();
  })
});

module.exports = Sync;
</code></pre>
        </article>
    </section>





        </div>
      </div>
    </div>

    <footer>
      <div>
        Documentation generated by
        <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.3</a>
        
          on
          June 7, 2020
        
      </div>
    </footer>

    <script>prettyPrint();</script>
    <script src="scripts/linenumber.js"></script>
    <script src="scripts/main.js"></script>
  </body>
</html>
